-- Copyright © 2019 Pedro Gimeno Fortea
-- This program may be used by anyone and may be freely copied and distributed
-- in verbatim form. Modifications for personal use are allowed.
-- NO IMPLIED WARRANTIES.

-- TODO: Decide a proper license, as the one above sucks.

-- Usage: luajit mode1to2.lua infile outfile

-- Reconstruct MODE2/2352 from a MODE1/2048 image.
-- The reconstruction does not include Reed-Solomon ECC.
-- It only includes the 32-bit CRC EDC.
-- This suffices for Mednafen's PSX module to accept the image as valid.

-- According to the spec, the poly is:
-- (x^16 + x^15 + x^2 + 1) * (x^16 + x^2 + x + 1)
-- which equals x^32 + x^31 + x^16 + x^15 + x^4 + x^3 + x + 1
-- but the x^31 term is in bit 0 of the byte
-- therefore the polynomial word is (in bit reverse order):
--   11011000000000011000000000000001
-- which in hex is: 0xD8018001

local ffi = require('ffi')
local bit = require('bit')

local bxor = bit.bxor
local band = bit.band
local blshift = bit.lshift
local brshift = bit.rshift

local CRCpoly = 0xD8018001
local CRCtable

local function makeCRCTable()
  -- Make a bit-reversal table
  local B = ffi.new('unsigned char[256]')
  for i = 0, 255 do
    B[i] = band(blshift(i, 7), 0x80)
         + band(blshift(i, 5), 0x40)
         + band(blshift(i, 3), 0x20)
         + band(blshift(i, 1), 0x10)
         + band(brshift(i, 1), 0x08)
         + band(brshift(i, 3), 0x04)
         + band(brshift(i, 5), 0x02)
         + band(brshift(i, 7), 0x01)
  end

  CRCtable = ffi.new('uint32_t[256]')
  for i = 0, 255 do
    local ShiftReg = B[i]
    for j = 0, 7 do
      ShiftReg = bxor(brshift(ShiftReg, 1),
                         band(CRCpoly, -band(ShiftReg, 1)))
      CRCtable[B[i]] = band(ShiftReg, 0xFFFFFFFF)
    end
  end
end


local function CRC(data)
  local crc = 0
  for i = 16, 2071 do
    crc = bxor(brshift(crc, 8), CRCtable[band(bxor(crc, data[i]), 0xFF)])
  end
  return crc
end


local function main()
  makeCRCTable()

  local buf = ffi.new('unsigned char[2352]')
  ffi.fill(buf, 2352)

  local f = io.open(arg[1], "rb")
  local size = f:seek("end")
  if size % 2048 ~= 0 then
    f:close()
    error("File length is not a multiple of 2048.")
  end

  f:seek("set")
  local data = ffi.new('unsigned char[?]', size)
  print("reading image")
  for i = 0, size - 1, 2048 do
    ffi.copy(data + i, f:read(2048))
    if i % 16777216 == 0 then
      collectgarbage() -- needs a bit of help
    end
  end
  f:close()
  print("processing")

  f = io.open(arg[2], "wb")

  -- 0-11 is sync data.
  ffi.copy(buf, "\0\255\255\255\255\255\255\255\255\255\255\0")
  -- 12, 13 and 14 are BCD minute/second/frame and are calculated per sector
  buf[15] = 2  -- CD-XA Mode 2
  -- 16-23 is the subheader. 20-23 is a copy of 16-19.
  -- 16 is file (?). 17 is channel. 18 is submode (bitfield). 19 is coding.
  -- No idea about what file/channel/coding are used for. Maybe audio/video.
  -- For submode, see Mednafen 0.9.41+dfsg sources, src/psx/cdc.cpp line 621.
  -- We set the submode on sectors 12-16 like in PSX discs; the rest as DATA.
  -- Not sure if it makes a difference.
  -- 24-2071 is the data (payload).
  -- 2072-2075 is CRC, calculated over the subheader + data without padding.
  -- Bit-reversed like CRC-32; little-endian.
  -- 2076-2247 is P-parity (Reed-Solomon ECC). Not calculated here.
  -- 2248-2351 is Q-parity (Reed-Solomon ECC). Not calculated here.
  -- For Reed-Solomon details see ECMA-130 and the Mednafen sources.
  local i = 0
  repeat
    do
      local msf_sector = brshift(i, 11) + 150
      buf[14] = msf_sector % 75
      buf[13] = (msf_sector - buf[14]) / 75 % 60
      buf[12] = (msf_sector - buf[13] * 75 - buf[14]) / (75 * 60)
    end

    do
      -- Convert to BCD
      local tmp = buf[12] % 10
      buf[12] = (buf[12] - tmp) / 10 * 16 + tmp

      tmp = buf[13] % 10
      buf[13] = (buf[13] - tmp) / 10 * 16 + tmp

      tmp = buf[14] % 10
      buf[14] = (buf[14] - tmp) / 10 * 16 + tmp
    end

    buf[18] = (i >= 0x6000 and i < 0x8000) and 0x20  -- FORM = 2
           or (i == 0x8000) and 0x09                 -- DATA, EOR
           or 0x08                                   -- DATA
    buf[22] = buf[18]

    ffi.copy(buf + 24, data + i, 2048)
    local crc = CRC(buf)
    buf[2072] = band(crc, 0xFF)
    buf[2073] = band(brshift(crc, 8), 0xFF)
    buf[2074] = band(brshift(crc, 16), 0xFF)
    buf[2075] = band(brshift(crc, 24), 0xFF)

    f:write(ffi.string(buf, 2352))
    i = i + 2048
    if i % 16777216 == 0 then
      collectgarbage()
    end
  until i == size
  f:close()
  print("done")
end

return main()
